package com.cqhfan.interactionservice.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cqhfan.core.constans.MqConstants;
import com.cqhfan.core.enums.LikedRecordTypeEnum;
import com.cqhfan.core.exceptions.BizException;
import com.cqhfan.core.utils.UserContext;
import com.cqhfan.interactionservice.mapper.LikedRecordMapper;
import com.cqhfan.interactionservice.service.LikedRecordService;
import com.cqhfan.mq.strategy.MqMessageHelper;
import com.cqhfan.pojo.interaction.dto.AddLikedRecordDTO;
import com.cqhfan.pojo.interaction.dto.LikedChangeDTO;
import com.cqhfan.pojo.interaction.po.LikedRecord;
import com.cqhfan.redis.constants.RedisConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * @author 眭传洪
 * @create 2025-08-14 16:17
 * 直接往redis存 后续通过定时任务写入到数据库
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class LikedRecordServiceImpl extends ServiceImpl<LikedRecordMapper, LikedRecord> implements LikedRecordService {

    private final StringRedisTemplate stringRedisTemplate;

    private final MqMessageHelper mqMessageHelper;

    @Override
    public void handleLikesChange(String type, Long handleNum) {
        // 从redis获取点赞数量小的前几十个
        String key = RedisConstants.LIKED.LIKES_QUANTITY_KEY_PREFIX + type;
        Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet().popMin(key, handleNum);
        if (CollUtil.isEmpty(tuples)) {
            return;
        }

        // 处理成bizId:点赞数的dto列表
        List<LikedChangeDTO> list = new ArrayList<>(tuples.size());
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            String bizId = tuple.getValue();
            Double quantity = tuple.getScore();
            if (bizId == null || quantity == null) {
                return;
            }
            list.add(LikedChangeDTO.of(Long.parseLong(bizId), quantity.longValue()));
        }

        // 发送mq消息到业务端处理
        mqMessageHelper.sendMessage(
                MqConstants.EXCHANGE.LIKED_TOPIC_EXCHANGE,
                StrUtil.format(MqConstants.KEY.LIKED_CHANGE_KEY, type),
                list
        );
    }

    @Override
    public void addLikedRecord(AddLikedRecordDTO body) {
        Long userId = UserContext.getUser();
        Boolean liked = body.getLiked();
        Long bizId = body.getBizId();
        Integer likedRecordType = body.getLikedRecordType();

        // 处理点赞或者取消点赞
        boolean success = liked ? like(bizId, userId) : unLike(bizId, userId);
        if (!success) {
            throw new BizException("处理失败");
        }

        // 统计点赞数量
        String key = RedisConstants.LIKED.LIKED_RECORD_KEY_PREFIX + bizId;
        Long quantity = stringRedisTemplate.opsForSet().size(key);
        if (quantity == null) {
            return;
        }

        // 将点赞数保存到sorted_set
        LikedRecordTypeEnum recordTypeEnum = LikedRecordTypeEnum.valueOf(likedRecordType);
        if (recordTypeEnum == null) {
            return;
        }
        stringRedisTemplate.opsForZSet().add(
                RedisConstants.LIKED.LIKES_QUANTITY_KEY_PREFIX + recordTypeEnum.getDesc(),
                bizId.toString(),
                quantity
        );
    }

    private boolean unLike(Long bizId, Long userId) {
        // 将点赞记录从bizId移除
        String key = RedisConstants.LIKED.LIKED_RECORD_KEY_PREFIX + bizId;
        Long count = stringRedisTemplate.opsForSet().remove(key, userId.toString());

        // 移除到登录用户的点赞记录
        key = RedisConstants.LIKED.USER_LIKED_RECORD_KEY_PREFIX + userId;
        Long removeCount = stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        return removeCount != null && removeCount > 0 && count != null && count > 0;
    }

    private boolean like(Long bizId, Long userId) {
        // 将点赞记录添加到bizId里面
        String key = RedisConstants.LIKED.LIKED_RECORD_KEY_PREFIX + bizId;
        Long count = stringRedisTemplate.opsForSet().add(key, userId.toString());

        // 添加到登录用户的点赞记录
        key = RedisConstants.LIKED.USER_LIKED_RECORD_KEY_PREFIX + userId;
        Boolean success = stringRedisTemplate.opsForZSet().add(key, bizId.toString(), System.currentTimeMillis());
        return BooleanUtil.isTrue(success) && count != null && count > 0;
    }

    /**
     *  核心逻辑：查询这些bizId的点赞记录里面有没有当前登录用户
     */
    @Override
    public Set<Long> listLikeStatus(List<Long> bizIds) {
        Long userId = UserContext.getUser(); // 这里通过feign转发可以拿到

        // 批量执行redis命令查询结果 命令执行的结果放在objects里面
        List<Object> isLikes = stringRedisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            StringRedisConnection stringRedisConnection = (StringRedisConnection) connection;

            // 批量执行set的sismeber命令即可查询
            for (Long bizId : bizIds) {
                String key = RedisConstants.LIKED.LIKED_RECORD_KEY_PREFIX + bizId;
                stringRedisConnection.sIsMember(key, userId.toString());
            }
            return null;
        });

        // 返回的是点赞了的bizId列表，isLikes为true的是需要返回的
        // stream流写法
        return IntStream.range(0, isLikes.size())
                .filter(i -> (boolean) isLikes.get(i))
                .mapToObj(bizIds::get)
                .collect(Collectors.toSet());

        /*
            for循环写法
            Set<Long> result = new HashSet<>(isLikes.size());
            for (int i = 0; i < isLikes.size(); i++) {
            boolean like = (boolean) isLikes.get(i);
            if (!like) {
                continue;
            }
            result.add(bizIds.get(i));
            }
            return result;
        */
    }
}
